数据类型(比如int 类型)有具体的、指定的值,比如0,1,-1,2,等等。你或许以为值就是数据类型的全部。但同样重要的还有对这些值执行的运算(操作)。int 类型的运算涉及+,-,*,/,%以及其他几个操作符和预定义库函数。不要将数据类型简单地视为值的集合。数据类型由值的集合以及为那些值定义的一组基本运算构成。

使用数据类型的程序员访问不了值和运算的实现细节,该数据类型就称为抽象数据类型(Abstract Data Type,ADT)。预定义类型(比如int)就是抽象数据类型(ADT)。对于int类型,你不知道+和-等运算具体如何实现。即使知道,也无法在任何C++程序中利用这方面的信息。

程序员定义的类型(比如结构类型和类类型)不会自动成为ADT。除非精心定义,并小心使用,否则程序员定义的类型在使用时可能显得非常“别扭”,造成程序难于理解和修改。为避免这些问题,最好的办法就是确保你定义的所有数据类型都是ADT。C++要用类达此目的,但并非每个类都是ADT。

为了将类定义成抽象数据类型,需严格区分其他程序员使用类型的方式与类型的实现细节。

(1) 所有成员变量都设为类的私有成员。

(2) 用户程序员需要的每个基本操作都设为类的公共成员函数,并完善地规定如何使用每个公共成员函数。

(3) 任何辅助函数都设为私有成员函数。

与普通函数的实现相似,ADT 的实现也应存在于“黑盒”中,从外面看不见其中的东西。

C++最强大的功能之一就是派生类的使用。继承其实只是派生类机制的另一种说法。一个类从另一个类派生时,除了继承后者的一切,还可添加自己的功能。例如,假设定义一个交通工具(Vehicle)类,它的成员变量记录了交通工具的轮数和最大乘客数。类还有取值和赋值函数。再假设定义一个汽车(Automobile)类,它有和交通工具类一样的成员变量和函数。但汽车类还增加了一些新东西,比如油箱容量和车牌号,以及一些新成员函数。不需要重复交通工具类的成员变量和函数。相反,利用C++继承机制,让汽车类继承交通工具类的所有成员变量和函数。

结构将不同类型的数据合并成一个(复合)数据值。

类将数据和函数合并成一个(复合)对象。

类的成员变量或成员函数既可以是公共的,也可以是私有的。如果是公共的,可以在类的外部使用;如果是私有的,只能在那个类的另一个成员函数的定义中使用。

函数形参可为类或结构类型。函数可返回类或结构类型的值。

类的成员函数可采取和普通函数一样的方式重载。

构造函数是类的成员函数,在声明该类的对象时自动调用。构造函数必须与定义它的类同名。

数据类型由一个值的集合以及为这些值定义的一组基本运算构成。

如果使用数据类型的程序员不需要知道那个类型的值和运算是如何实现的,就说该类型是抽象数据类型(ADT)。

在C++中实现抽象数据类型,一个办法是将类的所有成员变量都定义成私有,将运算作为公共成员函数来实现。

继承是指类和类之间的父/子关系。子类或派生类继承父类的成员。

将类的操作(比如输入、输出和取值函数等)作为类的成员函数来实现。但对于某些操作,更自然的做法是作为普通(非成员)函数来实现。

如果类有一套完整的取值函数,可利用取值函数定义一个普通函数来测试相等性,或执行其他任何需要依赖私有成员变量的计算。这样虽能访问私有成员变量,但效率堪忧。

但有一个办法可以为非成员函数赋予和成员函数一样的访问权限。让equal 函数成为DayOfYear 的友元,上述equal 定义就完全合法了。

类的友元函数不是这个类的成员函数,而是一个“友好”的函数,它能像成员函数那样访问类的私有成员。友元函数可直接读取成员变量的值,甚至能直接更改成员变量的值。换言之,在赋值语句中,可将私有成员变量放在赋值操作符的任何一侧。为了使函数成为友元函数,必须在类定义中把它命名为友元。在类定义中声明友元函数只需在函数声明前添加关键字friend。为了将友元函数添加到类定义中,需要列出它的函数声明,这和列出成员函数的声明没什么区别,只是必须在友元函数声明之前添加关键字 friend。注意友元不是成员函数,它本质上仍是普通函数,只是被特别授予了访问类的数据成员的权限。友元的定义和调用方式与普通函数无异。

从表面看,使所有基本函数都成为类的友元,就没必要在类中包括取值和赋值函数。毕竟,友元函数能访问私有成员变量,取值和赋值函数似乎多余。这种说法不完全错误。如果真的使世界上所有函数都成为类的友元,确实不需要取值和赋值函数。但是,让所有函数都成为友元不现实。

成员函数和友元函数作用很相似。事实上,有时甚至不好决定应该将函数作为类的友元,还是作为类的成员函数。大多数时候,一个函数既可作为成员函数,也可作为友元函数,并能以相同方式执行相同任务。但某些情况确实更适合使用成员函数,而另一些情况更适合使用友元函数(有时甚至更适合使用普通的、老式的函数)。以下两个简单的规则可帮你选择成员函数和非成员函数。

类定义要包含一系列特殊函数:成员函数、构造函数、析构函数、拷贝构造函数、重载操作符函数。

从本质上讲,面向对象编程就是将数据和操作数据的过程视为一个对象:一个有身份和特征的独立实体。

C++类是一个模板,用于创建对象(就如同用基本数据类型定义变量)。定义类后,便可像使用其他类型那样使用根据它创建的对象。

类是一系列捆绑在一起的变量和函数,其中的变量可以是任何其他类型,包括其他类。

变量构成了数据,而函数使用这些数据来执行任务。将变更和函数捆绑在一起称为封装。

类中的变量称为成员变量,或叫数据成员、实例变量,它们是类的组成部分。

类中的函数使用和修改成员变量,它们被称为类的成员函数或方法。与成员变量一样,成员函数也是类的组成部分,它们决定了类的对象能做什么。

1 声明类,使用关键字class,并在后面加上有关成员变量和成员函数的信息。类定义放在一组大括号{}内。

声明并不直接给成员变量分配内存,这只是告诉编译器,这个声明的类是什么样的:包含哪些数据以及能做什么?编译器根据类声明的成员变量,就知道这个类的对象所需要的内存空间。

根据基本数据类型声明的变量可以告诉编译器以下信息:

I 需要的内存空间;

II 取值的范围;

III 可以执行的操作(如可以使用什么运算符,使用运算符的规则及达到的效果);

在C++中,可自己定义类型,以模拟要解决的问题。要声明新类型,可创建一个类,类是新类型的定义,然后可以根据新类型定义新类型的变量(一般叫实例)。这样的一个过程类似于根据基本数据类型声明变量。

2 创建对象

创建对象,即分配了内存空间,并用对象名作为全部成员数据的首地址。

要根据类创建对象,可指定类名和变量名,类似根据基本数据类型创建变量。创建对象被称为实例化,对象是类的实例。

3 访问数据成员

对象名就是首地址,按成员数据名称就可以偏移到相应的内存单元,按其类型解析出数据。

创建对象后,可使用句点运算符(.)来访问其成员函数和成员变量。

用来设置或获取私有成员变量值的函数称为存取器(addcessor);要使用这些变量,其他类必须调用存取器,而不能直接使用。

存取器让您能够将数据的存储方式和使用方式分开。这样,如果修改了数据的存储方式,也无须重写使用数据的函数。

4 实现成员函数

同样,成员函数是过程的封装,是算法的实现所在。

对于声明的每个成员函数,都必须进行定义。

成员函数的定义以类名打头,然后是作用域解析运算符(::)和函数名。

类函数和常规函数类似,也可以接受参数并返回值。

四个特殊的成员函数:访问和设置私有成员变量值的存取器、构造函数、析构函数;

构造函数用于实例化对象时调用它来初始化数据成员,构造函数名与类名相同,且没有返回值,参数可有可无;

析构函数用于释放分配给对象的内存,析构函数的名称总是由腭化符号(~)和类名组成,无参数、无返回值、无语句;

pivate数据成员只能被成员函数访问。

public成员数据可以在类外被访问到。

private修改成员函数时,该成员函数只能被其它成员函数访问。

当在类的定义之外定义成员函数时,需要使用域解析操作符“::”如:

返回值类型 类名::成员函数名(参数表);

void fun()const;

const表示该函数可以被常量对象调用, 例如 const ClassA a; a.fun() 常量对象只能调用使用const的函数

表示该函数可以操作成员,但不可以修改数据成员的内容。 假设在fun中 给成员变量赋值,则会出错。

编程思维方式:面向对象思维

1 描述对象:说明求解问题需要哪些对象,这些对象具有什么属性和行为能力;针对描述对象,C++语言提供了类class的语法设施用来描述对象的属性和行为能力。类描述了具有相同属性和行为能力的一类对象,称为对象类型,简称类。在类中,对象的属性称为类的数据成员,对象的行为能力称为灰的成员函数。

(1) 描述对象产生和消亡的行为:C++函数提供了构造函数和析构函数。

(2) 描述对象运算操作的行为:C++语言提供了操作符重载函数。

(3) 描述对象的其他行为:普通的成员函数。

C++语言提供了组合和继承两种语法工具,用于支持对象之间组合和类属关系的表达。

2 描述沟通协作过程:说明如何生成具体的对象,以及这些对象之间如何沟通协作发挥各自的行为能力实现问题求解的过程。

类的私有成员只能被本类的方法或者申请友元函数,但可以不需定义类的对象,直接使用类的私有成员函数。

派生类可以继承原有类的成员数据和函数,同时可以添加新的成员。

类其实就是一类数据,封闭或抽象了一类数据和对这些数据操作的方法;类实例化为对象后就可以利用这些数据或方法进行数据处理,形成数据输出。

类:数据的二次分类;将程序分为不同的部件,不同的部件即属于不同的类,将基础类型的数据和对这些数据的处理方法封装在一起,形成类型,类型实例化为类,就像变量的定义或部件的制造;

类除了创建对象,还可以派生子类;

过程的封装:函数;

过程和数据的封装:类类型;

C++ this是指向当前对象的指针;

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,...

{派生类成员声明;};

C++ 允许动态创建 const 对象:

// allocate and initialize a const object

const int *pci = new const int(1024);

定义一个类时,也就是定义了一个类型。一旦定义了类,就可以定义该类型的对象。定义对象时,将为其分配存储空间,但(一般而言)定义类型时不进行存储分配。(静态数据成员一般也是在类外在.cpp文件而不.h文件中初始化其值)

static 成员可以是函数或数据,独立于类类型的对象而存在。

如果没有继承,类只有两种用户:类本身的成员和该类的用户。将类划分为 private 和 public 访问级别反映了用户种类的这一分隔:用户只能访问 public 接口,类成员和友元既可以访问 public 成员也可以访问 private 成员。

有了继承,就有了类的第三种用户:从类派生定义新类的程序员。派生类的提供者通常(但并不总是)需要访问(一般为 private 的)基类实现,为了允许这种访问而仍然禁止对实现的一般访问,提供了附加的 protected 访问标号。类的 protected 部分仍然不能被一般程序访问,但可以被派生类访问。只有类本身和友元可以访问基类的 private 部分,派生类不能访问基类的 private 成员。

变量的值存储在计算机内存中,而内存被组织成按顺序编号的内存单元,其中每个内存单元都有地址。指针是指向内存地址的特殊变量。

指针能够让您能够在程序中直接操作计算机内存。知道数据的内存地址后,无须使用变量就能访问-使用指向该地址的指针。

使用指针完成一些特定的任务时,比使用变量更合理。

指针很重要,因为它们用于存储堆中的对象地址以及按引用传递参数。

堆中的对象在函数返回后仍存在。另外,这种对象还是动态的,将对象存储在堆中的功能让您能够在运行阶段决定需要多少个对象,而不必预告声明。在堆中声明的对象可用来创建复杂的数据结构。

在栈中创建(实例化)对象:

Cat helloKitty;

在堆中创建对象:

Cat *phelloKitty = new Cat

对于在栈中创建的Cat对象,使用句点运算符(.)来访问其成员数据和成员函数;要访问堆中的Cat对象,必须对指针解除引用,并对指针指向的对象使用句点运算符。因此,要访问成员函数GetAge,可编写如下代码:

(*phelloKitty).GetAge();

也可以使用一种简捷的间接访问运算符,指向运算符(points-to operator,->),它由短划线(-)和大于号(>)组成,C++将它们视为一个符号。

在C++中,可自己定义类型,以模拟要解决的问题。要声明新类型,可创建一个类,类是新类型的定义,然后可以根据新类型定义新类型的变量(一般叫实例)。这样的一个过程类似于根据基本数据类型声明变量。

当方法的定义放置在类体外时,方法名称前需要使用类名和域限定符“::”来标记方法属于哪一个类;

成员函数是类与外部程序的接口。

在类体外定义函数时,需要在函数名前加类名和作用域运算符::。

类的静态成员和成员函数:使用static函数;

class派生类名:[继承方式] 基类名{};

多继承使用逗号运算符;

类的成员有static修饰时,表现其不属于特定的对象,而是属于对象。

实例化和继承(子类)是类的两种发展方向。

private除了不能被外部访问以外,还不能被继承,而protected是可以被继承的。

类一般的思路是隐藏数据,暴露方法(方法做为接口);

基本类型与类

变量与对象

基本类型的值域与类的属性;

基本类型的操作符与类的方法;

以下三条语句的作用是相同的:

void Point::setx(double inputx)
{
    x=inputx;
    this->x = inputx;
    (*this).x = inputx;
}

结构的执行效率相对较高。结构没有析构函数。结构不可以继承。一般来说结构用来处理较少的程序基础数据,而类用来处理复杂逻辑。(对象new到空间,直接声明也是在栈中)

对象和基本数据类型一样,定义在栈中,new在堆中。

有的时候,基类并不需要与具体的事物关联起来,例如动物,它是一个抽象的概念,表示所有的动物,可以派生出企鹅,猴子等。C++引入了抽象类(abstract class)的概念来为各种派生类提供一个公共的界面。

抽象类可以提供多个派生类共享基类的公共定义,它可以提供抽象方法,也可以提供非抽象方法。抽象类不能实例化,必须通过继承由派生类实现其抽象方法,也就是说,对抽象类不能使用new关键字,它也不能被封装。如果抽象类的派生类没有实现所有的抽象方法,则该派生类也必须声明为抽象类。派生类使用覆盖(overriding)来实现抽象方法。

抽象类一定包含有纯虚函数,因此不能定义抽象类的对象。

多态是面向对象的重要特性之一,它是一种行为的封装,简单的描述就是“一个接口, 多种实现”,也就是同一种事物所表现出的多种形态。

编写程序实际上就是一个将世界的具体事物进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来,再通过这个抽象的事物,与不同的具体事物进行对话。

类不仅限于容纳数据,还可以定义成员函数,甚至可定义在类对象之间使用标准C++运算符执行的操作(操作符重载)。

结构化的面向过程的编程是根据与计算机相关的数据类型分析问题,而面向对象的编程是根据与问题相关的数据类型(类)进行编程。

这些类都是为解决某种问题而专门定义的,其中包括为处理该类型的实例而需要的函数和运算符。

这样,面向对象的程序设计过程首先要确定解决手头的问题需要哪些新的专用数据类型,并把它们定义为类,然后根据与该问题相关的具体类型可以执行的操作编写程序。

定义类时,就是在定义某种数据类型的蓝图。我们没有实际定义任何数据,但确实定义了类名的意义:即类对象将由哪些数据组成,对这样的对象可以执行什么操作。

类的访问控制:

public:这些成员的类对象的作用域内的任何位置都可以访问它们。private和protected的成员不能在类的外部访问。

类接口应当充分覆盖到那些人们有可能对类对象做的事情,而且应该尽量以防止误用或偶然性错误的方式实现。

类接口就是public成员函数;

派生类不继承的基类成员仅有析构函数、构造函数以及任何重载赋值运算符的成员函数。

派生类成员函数不能访问基类的private数据成员;

基类的保护成员可以由派生类的成员函数来访问。

继承也有访问控制的继承方式,如:CABox:protected CBox;

友元是对私有成员的突破,可以访问私有成员,也就是private定义的成员。

基类中的函数可以声明为virtual,这样将允许在执行时根据调用该函数的当前对象的类型,选择派生类中出现的该函数的其他定义。

在派生类中用override修饰符定义虚函数时,编译器会验证直接或间接基类是否包含签名相同的虚函数,如果不包含,就生成一个错误信息。

如果类的函数成员用final修饰符指定,派生类就能重写该函数。否则编译器会生成一个错误消息。

如果类用final修饰符定义,该类就不能用作另一个类的基类。尝试把final类用作基类,编译器会生成一个错误消息。

可以将类指定为另一个类的friend。这种情况下,friend类的所有函数成员都可以访问另一个类的所有成员。如果类A是类B的friend,则类B并非是类A的friend,除非它是这样声明的。

通过在函数声明最后添加=0,可以将基类中的虚函数指定为纯虚函数。这样,该类就成不为能创建任何对象的抽象类。在任何派生类中,都必须定义所有纯虚函数。如果不是,则该派生类也将成为抽象类。


将函数放入结构体是从C到C++的根本改变

在C中,结构体只是将一组相关的数据捆绑了起来,它除了是程序逻辑更加清晰之外,对解决问题没有任何帮助。

将处理这组数据的函数也加入到结构体中,结构体就有了全新的功能。它既能描述属性,也能描述对属性的操作。事实上,它就成为了和内置类型一样的一种全新的数据类型。

为了表示这是一种全新的概念,C++用了一个新的名称 — 类来表示。

也可以将成员函数的定义直接写在类定义中,这些函数被默认为内联函数。

类定义并不分配空间,空间是在定义对象时分配

组合就是把用户定义类的对象作为新类的数据成员

组合表示一种聚集关系,是一种部分和整体(is a part of)的关系

必须用初始化列表去初始化对象成员

继承是面向对象程序设计的一个重要特征,它允许在已有类的基础上创建新的类

基类、父类

派生类、导出类或子类

继承可以让程序员在已有类的基础上通过增加或修改少量代码的方法得到新的类,从而较好地解决代码重用的问题。

多态性:不同对象收到相同的消息时产生不同的动作。即用一个名字定义不同的函数

静态联编:编译时已决定用哪一个函数实现某一动作。

动态联编:直到运行时才决定用哪一个函数来实现动作

函数重载:用同一名字实现访问一组相关的函数

运算符重载

重载函数是通过“名字压延”方法来实现。即在编译时将函数名和参数结合起来创造一个新的函数名,用新的名字替换原有名字。

面向对象程序设计的一个重要的目标是代码重用。

代码重用的两种方法:组合和继承。

组合是将某一个已定义类的对象作为当前类的数据成员,则对此数据成员操作的代码得到了重用。

继承是在已有类(基类)的基础上加以扩展,形成一个新类,称为派生类。在派生类定义时,只需要实现扩展功能,而基类有的功能得到了重用。

面向对象方法是用类的层次结构来体现类之间的继承和发展。而面向过程则强调过程的抽象化与模块化。

继承一般有三种形式

1 实现继承:派生类使用基类的属性和方法而无需额外编码;

2 可视继承:子窗体使用父窗体的外观和实现代码;

3 接口继承:仅使用属性和方法,实现滞后到子类实现;

继承的过程是从一般到特殊的过程。

私有公有除了其本身的成员方法以外,不能被外部访问,派生类亦是如此,但可以通过成员函数来访问,但函数访问是有成本的,一个变通的方法就是,将这一类数据成员声明为protected,这样,派生类就可以访问这一类成员了。

按值传递,一方面是数据的保护机制,另一方面也是标识符名称的重复利用机制;

构造函数就是数据成员的初始化机制;

类的静态成员在类外定义,对于类来说,它具有全局性,是一种类限定的全局性。

类的对象是使用构造创建和初始化。当声明对象时,将自动调用构造函数。构造函数可以重载,以提供初始化对象的不同方法。

类成员可以指定为public,此时,程序中的任何函数都可以自由访问它们。类成员还可以指定为private,此时,只能由同类的成员函数或友元函数访问。

当数据成员中有动态内存分配的指针变量时,类的定义与实现会增加其复杂性,首先是复制构造函数,然后是析构函数;涉及到指针变量值传递还是址传递,以及如何delete动态分配的内存。

如果动态地为类的成员分配空间,则必须实现复制构造函数。同时还要实现析构函数。除非实现智能指针。

函数对象是重载操作符()(通过在类中实现函数operator()())的类型的对象。

类的继承和派生通过接受已有类的成员达到代码可重用和可扩展的目的。

派生类在基类的基础构架上构架;派生类对象可以直接赋值给基类对象;基类指针或引用可以指向派生类对象。

override描述符明确指明成员是用于覆盖的。

final运算符表明不使用派生类;

多出来的参数,就是所谓的this 指针。至于类别之中,成员函数的定义:

class CShape
{
    ...
    public:
    void setcolor(int color) 
    { m_color = color; }
};
//被编译器整治过后,其实是:
class CShape
{
    ...
    public:
    void setcolor(int color, (CShape*)this) 
    { this->m_color = color; }
};

什么样的成员函数应被说明为公有的?什么样的成员函数应被设为私有的?

根据对象行为提取的成员函数应该设为公有的成员函数。公有函数的实现时分解出一些小函数通常被设计为私有的成员函数。

什么是this指针?为什么要有this指针?

每个对象包含两部分内容:数据成员和成员函数。不同的对象有不同的数据成员值,因此每个对象都拥有一块保存自己数据成员值的空间。但同一类的所有对象的成员函数的实现都是相同的,因此所有的对象共享了一份成员函数的代码。这又带来了另一个问题:成员函数中的涉及到的数据成员到底是哪个对象的数据成员?为此C++让每个成员函数都包含了一个隐含的参数this,该参数是一个指针,指向当前调用该成员函数的对象。成员函数中涉及的数据成员都是this指针指向的对象的数据成员。

如果类的设计者在定义一个类时没有定义任何成员函数,那么这个类有几个成员函数?

四个函数:默认的构造函数、默认的复制构造函数、析构函数和赋值运算符重载函数。

如何实现类类型对象到内置类型对象的转换?如何实现内置类型到类类型的转换?

内置类型到类类型的转换是通过构造函数。如果类有一个只带一个参数的构造函数,则可以实现参数类型到类类型的隐式转换。类类型到内置类型或其他类类型的转换必须通过类型转换函数。

如何禁止内置类型到类类型的自动转换?

在构造函数原型前加一个保留词explicit。

什么是组合?什么是继承?is-a的关系用哪种方式解决?has-a的关系用哪种方法解决?

组合是将某个类的对象作为当前正在定义的类的数据成员,反映的是has-a的关系。继承是在一个类的基础上,通过增加数据成员或成员函数而得到一个功能更强大的类,反映的是is-a的关系。

protected成员有什么样的访问特性?为什么要引入protected的访问特性?

protected成员是允许派生类的成员函数可以访问的私有成员。可以提高派生类成员函数的效率。

什么是抽象类?定义抽象类有什么意义?抽象类在使用上有什么限制?

包含有纯虚函数的类称为抽象类。定义抽象类的主要用途是规范从这个抽象类派生的这些类的行为。在使用时,不能定义抽象类的对象,只能定义抽象类的指针。

为什么要定义虚析构函数?

将析构函数定义成虚函数可以防止内存泄漏。

试说明派生类对象的构造和析构次序。

构造时,先执行基类的构造函数,再执行派生类自己的构造函数。析构时,先执行派生类的析构函数,再执行基类的析构函数。

试说明虚函数和纯虚函数有什么区别。

虚函数有函数体,可以执行,也可以作为实现多态性的一种手段。而纯虚函数没有函数体,是不可以执行的。

基类指针可以指向派生类的对象,为什么派生类的指针不能指向基类对象?

由于派生类对象中包含了一个基类对象,当基类指针指向派生类对象时,通过基类指针可访问派生类中的基类部分。但如果用一个派生类指针指向基类对象,通过派生类指针访问派生类新增加的成员时,将无法找到这些成员。

如果一个派生类新增加的数据成员中有一个对象成员,试描述派生类的构造过程。

构造派生类对象时,先调用基类的构造函数,再调用对象成员的构造函数,最后执行派生类的构造函数。

组合就是把用户定义类的对象作为新类的数据成员

组合表示一种聚集关系,是一种部分和整体(is a part of)的关系

必须用初始化列表去初始化对象成员

继承是面向对象程序设计的一个重要特征,它允许在已有类的基础上创建新的类

基类、父类

派生类、导出类或子类

继承可以让程序员在已有类的基础上通过增加或修改少量代码的方法得到新的类,从而较好地解决代码重用的问题。

总结类的一些特征

类具有结构的所有特征,还具有与成员函数有关的所有特征。

类同时具有成员变量和成员函数。

成员(无论成员变量还是成员函数)可为公共或私有。

通常,类的所有成员变量都应标记为私有成员。

类的私有成员只能在同一个类的另一个成员函数的定义中使用。

类的成员函数的名称可像普通函数名称那样重载。

一个类可将另一个类作为自己的成员变量的类型使用。

函数形参可以是类类型。

函数可返回对象;换言之,类可以是函数返回值的类型。

声明对象时,经常需要初始化它的部分或全部成员变量。“初始化成员变量”是最常见的一种初始化操作。C++为这种初始化准备了特殊机制。定义类时,可定义一种特殊的成员函数,称为构造函数。构造函数在声明类的对象时自动调用。构造函数通常初始化成员变量的值,并进行其他初始化操作。可像定义其他任何成员函数那样来定义构造函数,只是注意以下两点。

(1) 构造函数必须与类同名。例如,假定类名是BankAccount,则该类的构造函数必须命名为BankAccount。

(2) 构造函数定义不能返回值。此外,在函数声明起始处或函数头中,不允许指定返回类型(连void 都不可以)。

例如,假定像下面这样定义类:

class SampleClass
{
public:
    SampleClass(int parameter1, double parameter2);
    void do_stuff();
private:
    int data1;
    double data2;
} ;

为了声明SampleClass 类的对象并调用构造函数,以下语句完全合法:

SampleClass myObject(7, 7.77);

但如果告诉你以下语句非法,你或许会感到惊讶:

SampleClass yourObject;

获取两个实参的构造函数

编译器将上述声明解释成要调用无参构造函数,但类定义中无此函数。要么必须在yourObject 的声明中添加两个参数,要么必须定义一个无参构造函数。

可在无实参的情况下调用的构造函数称为默认构造函数。声明对象而不提供任何实参,就会默认调用它。由于经常都要在不提供构造函数实参的情况下声明对象,所以始终应该包括一个默认构造函数。下面是SampleClass 的修订版本,其中包括一个默认构造函数:

class SampleClass
{
public:
    SampleClass(int parameter1, double parameter2);
    SampleClass();
    void do_stuff();
private:
    int data1;
    double data2;
} ;

以这种方式重新定义了SampleClass 类之后,前面的yourObject 声明就合法了。

如果不希望默认构造函数初始化任何成员变量,只需在实现时提供一个空主体。下面的构造函数定义完全合法。调用时,除了让编译器不报错,它不会做其他任何事情:

SampleClass::SampleClass()

{

// 什么都不做

}

使用new 操作符创建类类型的动态变量会调用类的默认构造函数。

定义一个成员函数需要考量:

1 运算符函数还是非运算符函数?
2 自由运算符还是成员运算符?
3 虚函数还是非虚函数?
4 纯虚成员函数还是非纯虚成员函数?
5 静态成员函数还是非静态成员函数?
6 常量成员函数还是非常量成员函数;
7 公共的还是受保护的;
8 通过值、引用还是指针返回?
9 返回常量还是非常量?
10 参数是可选的还是必需的?
11 通过值、引用还是指针传递参数?
12 将参数作为常量传递还是非常量传递?
13 友元函数还是非友元函数?
14 内联函数还是非内联函数?

数据抽象就是隐藏实现细节,暴露接口。如类声明或定义在.h文件中的public部分,或函数声明就是公开的接口,而在.cpp文件中的函数实现或成员方法的实现就可以隐藏起来,由实现者或开发者去实现,使用者只需关心头文件中的内容,了解如何实现即可。

基本数据类型,或定义良好的类类都是数据抽象的实例。

数据抽象有两个重要的优势:

1 类的内部受到保护,不会因为无意的用户级错误导致对象状态受损;

2 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。

也就是说,保持了接口的稳定性,即使实现不断更新或修改,也不会影响到这个接口者去更改代码。

数据抽象:只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。数据抽象是一种依赖于接口和实现分离的编程(设计)技术。

2

继承是面向对象的主要特征(此外还有封装和多态)之一,它使得一个类可以从现有类中派生,而不必重新定义一个新类。例如,定义一个员工类,它包含员工ID、员工姓名、所属部门等信息,再定义一个操作员类,通常,操作员属于公司的员工,因此它也包含员工ID、员工姓名、所属部门等信息,此外还包含密码信息、登录方法等。如果当前已经定义了员工类,在定义操作员类时可以将其从员工类派生一个新的员工类,向其中添加密码信息,登录方法等就可以了,不必重新定义员工ID、员工姓名等信息了因为它已经继承了员工类的信息。

谓类域是指类的作用域。我们在定义一个类时,类体就是类的作用域,即类域。类的所有成员均处于类域中。当程序中使用点运算符(.)和箭头运算符(->)访问类成员时,编译器会根据运算符前面的对象的类型来确定其类域,并在其类域中查找成员。如果使用域运算符(::)访问类成员,编译器将根据运算符前面的类名来确定其类域,查找类成员。这样,当用户通过对象访问一个不属于类成员的“成员”时,编译器将提示其“成员”没有在类中定义,因为在类域中找不到该成员。

在定义类时,类成员的声明顺序也是很主要的,先声明的成员不能够使用后声明的成员。

类的定义也可以放置在函数中,这样的类被称之为局部类。

变量的声明(类型和名字的引入)和定义(空间的分配)。

类的定义(类的成员声明)和实现(成员函数的实现、静态数据成员的初始化)、类的实例化。

空类的长度是1byte;

定义了任一的构造函数就没有默认的构造函数。

类成员:约定对象的属性和行为。

声明和定义对象,包括属性(内存映像)、行为(附加的函数)、生(初始化)、死(资源释放);

OO三大特征

1 用类机制实现封装

类是对对象属性和行为的抽象,是抽象数据类型的最佳实现,抽象数据类型允许用户在不了解数据类型在计算机中的表示方式的情况下操作数据类型。换句话说,就用户而言,他需要知道的只是可以对数据类型执行的操作。实现数据类型的人可以自由地更改其实现,而不会影响用户。可以通过接口实现定义与使用的分离。

2 用基类和派生类实现继承

继承的派生类可以保留、改进基类的属性和行为,并添加新的属性和行为。

继承能够实现设计与代码重用。

3 通过虚函数实现运行期多态

虚函数所引起的编译器行为,包括为类添加虚函数表、为对象添加调用虚函数表的函数指针,并改写调用虚函数的方式,也就是通过函数指针去调用。

运行期多态可以实现接口统一、高度复用,向后兼容、灵活扩展;

封装。封装将客观举物抽象成类,每个类对自身的数据和方法实行访问控制,通过关键字(private、protected和public)来控制外界对类成员的访问,以达到保护数据和方法的目的。

继承。子类从父类继承,从而获得父类的属性和方法。广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无须额外的编码能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合成接口继承及纯虚函数)构成功能复用的两种方式。

当对象之间是类别而不是包含关系时,就可以考虑使用继承。

多态。多态将父对象设置成和一个或更多的它的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单来说就是一句话:允许将子类类型的指针赋值给父类类型的指针。

面向对象方法中的抽象是指对具体问题即对象进行概括,抽出一类对象的共性并加以描述的过程。面向对象的软件开发中,首先应该对要解决的问题抽象成类,然后才是解决问题的过程。抽象有两个方面:数据抽象和行为抽象。数据抽象是描述某类对象的属性或状态,行为抽象是描述某类对象的共同行为或共同功能。

把抽象出来的数据成员和函数成员结合形成一个整体,就是封装。封装的时候,我们可以把一些成员作为类和外界的接口,把其他的成员隐藏起来,以达到对数据访问权限的控制,这样可以使程序的各个部分改变时最低程度的影响其他部分,程序会更安全。

把数据和函数封装为一个可复用的模块,开发时可以利用已有的成果而不必每次都重复编写。我们只需要通过类提供的外部接口访问模块,并不需要知道内部的细节。C++中就是利用类的形式来实现封装的。

class  Clock                                                  // class是关键字 Clock是类名
       {
       public:                                                // 提示下面是外部接口
              void SetTime(int NewH,int NewM,int NewS);       // 行为,函数成员
              void ShowTime();                                // 行为,函数成员
       private:                                               // 特定的访问权限
              int Hour,Minute,Second;                         // 属性,数据成员          
      };

这是一个完整的类的声明。它声明了一个名为Clock的类,其中的数据成员和函数成员是前面分析得到的抽象结果。关键字public和private是用来指定成员的不同访问权限的。声明为public的两个函数为类提供了外部接口,外界只能通过这两个接口跟Clock类联系。声明为private的三个整型数据是类的私有数据,外部无法直接访问。我们可以看到,这种访问权限的机制有效实现了对数据的隐藏。

在面向过程的设计中,程序的模块是函数构成的,而面向对象设计中程序模块是类构成的。函数只是语句和数据的封装,而类是函数与数据的封装,对比下肯定是面向对象设计更重量级了,更适合大型程序的开发。

在我们对现实中的某些事物抽象成类时,可能会形成很复杂的类,为了更简洁的进行软件开发,我们经常把其中相对比较独立的部分拿出来定义成一个个简单的类,这些比较简单的类又可以分出更简单的类,最后由这些简单的类再组成我们想要的类。比如,我们想要创建一个计算机系统的类,首先计算机由硬件和软件组成,硬件又分为CPU、存储器等,软件分为系统软件和应用软件,如果我们直接创建这个类是不是很复杂?这时候我们就可以将CPU写一个类,存储器写一个类,其他硬件每个都写一个类,硬件类就是所有这些类的组合,软件也是一样,也能做成一个类的组合。计算机类又是硬件类和软件类的组合。

类的组合其实描述的就是在一个类里内嵌了其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。简单说,一个类中有若干数据成员是其他类的对象。以前的教程中我们看到的类的数据成员都是基本数据类型的或自定义数据类型的,比如int、float类型的或结构体类型的,现在我们知道了,数据成员也可以是类类型的。

如果在一个类中内嵌了其他类的对象,那么创建这个类的对象时,其中的内嵌对象也会被自动创建。因为内嵌对象是组合类的对象的一部分,所以在构造组合类的对象时不但要对基本数据类型的成员进行初始化,还要对内嵌对象成员进行初始化。

组合类构造函数定义(注意不是声明)的一般形式为:

类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),...
{
    类的初始化
}

1.类,包括数据成员和函数成员。

2.对象,类的实例。

3.类及对象的关系,继承或者包含。

4.类及对象之间的联系,相互作用与消息传递等。

类作用域

假设有一个类A,A中有一个数据成员x,x在A的所有函数成员中都有效,除非函数成员中也定义了一个名称为x的变量,这样的x就具有类作用域。为什么要排除含有另一个名称也为x的变量的函数成员呢?因为那样的话A的数据成员x在此函数成员被函数成员中的另一个x覆盖,不可见了。

函数成员访问的大多数数据成员都具有类作用域。我们一般用a.x的方式访问类A的对象a的数据成员x,这里的x就具有类作用域。

符号“.”用于访问对象的成员,包括函数成员。比如,a.fun(x)用来调用对象a的函数fun。如果ptr是指向类A的一个对象的指针,则访问其数据成员x的方式为ptr->x,访问函数成员的形式如:ptr->fun(x)。

四种作用域中,最大的是文件作用域,其次是类作用域,再次是块作用域。

在结构化程序设计中,有人提出“数据结构+算法=程序设计”,数据结构就是数据的组织,算法是用函数实现的,可见数据和函数很早就被看作程序设计的重点了。面向对象程序设计中,这种观点应稍作一下修改:“数据结构+算法=对象”。就是数据和函数构成了类的对象。

面向对象程序设计中,数据用来描述对象的属性,函数是行为,用来处理数据。将数据和函数封装到一个类里,类中的函数成员可以访问数据成员,函数成员之间可以实现数据共享。

我们可以通过基类名和作用域分辨符来访问基类中的同名成员。作用域分辨符就是“::”,在派生类内部访问基类同名成员的语法形式是:

基类名::数据成员名;//数据成员

基类名::函数成员名(参数表);//函数成员

如果是在派生类外通过派生类对象访问的话,前面还要加上“派生类对象名.”:

派生类对象名.基类名::数据成员名;//数据成员

派生类对象名.基类名::函数成员名(参数表);//函数成员

这里的基类名就限定了后面的成员属于哪个类。

每个类对象在实例化时都有一个this指针指向其数据的首地址。当类的非静态成员函数在访问非静态成员变量时,若遇到形差变量和成员变量相同,可以使用this指针指向成员变量以示区别。

This not only provides a single unified point of entry into a library component, but it also hides the names of the functions within the class name. Access control (implementation hiding) was introduced. This gives the class designer a way to establish clear boundaries for determining what the client programmer is allowed to manipulate and what is off limits. It means the internal mechanisms of a data type's operation are under the control and discretion of the designer of the class, and it's clear to client programmers what members they can and should pay attention to.

Two of these safety issues are initialization and cleanup. A large segment of C bugs occur when the programmer forgets to initialize or clean up a variable. This is especially true with C libraries, when client programmers don’t know how to initialize a struct, or even that they must.

Guaranteed Initialization with the Constructor

Guaranteed Cleanup with the Destructor

传统的面向过程程序设计是围绕功能进行的,用一个函数实现一个功能。所有的数据都是公用的,一个函数可以使用任何一组数据,而一组数据又能被多个函数所使用。程序设计者必须考虑每一个细节,什么时候对什么数据进行操作 。

面向对象程序设计采取的是另外一种思路。它面对的是一个个对象。实际上,每一组数据都是有特定的用途的,是某种操作的对象。也就是说,一组操作调用一组数据。

对象 = 算法 + 数据结构

程序=(对象+对象+对象+……)+ 消息

当我们定义一个类时,可以在类中直接定义函数体。这时成员函数在编译时是作为内联函数来实现的。

同时,我们也可以在类体外定义类的内联成员函数,在类体内说明函数,在类体外定义时,在成员函数的定义前面加上关键字inline。

类体的区域称为类作用域。类的成员函数与成员数据,其作用域都是属于类的作用域,仅在该类的范围内有效,故不能在主函数中直接通过函数名和成员名来调用函数。

类类型的作用域:在函数定义之外定义的类,其类名的作用域为文件作用域;而在函数体内定义的类,其类名的作用域为块作用域 。

对象的作用域与前面介绍的变量作用域完全相同 , 全局对象、局部对象、局部静态对象等。

当定义了一个类,这个类只能用作基类来派生出新的类,而不能用这种类来定义对象时,称这种类为抽象类。当对某些特殊的对象要进行很好地封装时,需要定义抽象类。

将类的构造函数或析构函数的访问权限定义为保护的时,这种类为抽象类。

构造函数不能被继承,派生类的构造函数必须调用基类的构造函数来初始化基类成员基类子对象。

转换函数就是在类中定义一个成员函数,其作用是将类转换为某种数据类型。

operator int( ){ return i; }

Data abstraction allows you to create new types from scratch, but with composition and inheritance, you can create new types from existing types.

面向对象是以对象的集合类作为处理问题的基本单位。

类是一等公民:类实例可以作为函数的参数和返回值,可以在内存的任何域中出现:堆、栈、全程变量区。

Objects contain their own data and algorithms.

The concept object include class and instance.

C++语言中的类是可以组成层次结构的,类是用于描述事物的属性和对事物的操作,类与类之间有相对的独立性,但其可以通过一些方法进行信息的通信。


类的成员函数是对类中数据成员操作的实现,成员函数也是函数的一种,和一般的函数一样。成员函数也可以定义函数类型,具有参数表,有返回类型。

在C++中,当一个函数成员被调用时,系统自动地传递一个隐含的参数给函数成员,该隐含参数是一个指向接收该函数调用的对象的指针,于是,函数成员就知道该对哪个对象进行操作。在程序中,可以使用关键字this来引用该指针,因而称为this指针。this是一个隐含于每一个类的成员函数中的特殊指针,该指针是一个指向正在被某个成员函数操作的对象的指针。

当对一个对象调用成员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数,每次成员函数存取数据成员时,则隐含使用this指针。通常不显式地使用this指针来引用数据成员,可以使用*this来标识调用该成员函数的对象。

在c语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。在c++中,通过抽象数据类型(abstract data type,ADT),在类中定义数据和函数,来实现数据和函数直接的绑定。

成员函数和友元函数可以访问一个类的私有部分;

成员函数位于类的作用域之内;

非静态成员函数必须经由一个对象去激活;

class

1 base address, a calss namespace;

2 class access, including static and friend member;

3 member function how to operato member data, including initialization, clean up dynamic memory, copy, assign, move, etc.

It turns out that there are three different ways to overload operators: the member function way, the friend function way, and the normal function way.

public members can be accessed by anybody. Private members can only be accessed by member functions of the same class or friends. This means derived classes can not access private members of the base class directly!

Remember that derived has a Base part and a Derived part. When we assign a Derived object to a Base object, only the Base portion of the Derived object is copied. The Derived portion is not.

In the example above, base receives a copy of the Base portion of derived, but not the Derived portion. That Derived portion has effectively been “sliced off”. Consequently, the assigning of a Derived class object to a Base class object is called object slicing (or slicing for short).

A method is basically a behavior. A class can contain many methods. It is in methods where the logics are written, data is manipulated and all the actions are executed.

By defining data members only in the private section of the class, the class author is free to make changes in the data. If the implementation changes, only the class code needs to be examined to see what affect the change may have. If data is public, then any function that directly access the data members of the old representation might be broken.